<script lang="ts" setup>
import { computed, defineProps, onMounted, ref, withDefaults } from 'vue'

interface Props<T> {
  data: T[]
  itemMinWidth?: number // 最小宽度(px)
  itemHeight: number
  listHeight: number
  buffer?: number
  itemKey?: string
}
const props = withDefaults(defineProps<Props<any>>(), {
  itemMinWidth: 80,
  buffer: 8,
  itemKey: '',
})
const scrollTop = ref(0)
const windowWidth = ref(375)

onMounted(() => {
  // 获取屏幕宽度
  uni.getSystemInfo({
    success: (res) => {
      windowWidth.value = res.windowWidth
    },
  })
})

// 计算每行可放几个
const colNum = computed(() =>
  Math.floor(windowWidth.value / props.itemMinWidth),
)
// 实际item宽
const colWidth = computed(() =>
  windowWidth.value / colNum.value,
)
// 一共多少行
const rowNum = computed(() =>
  Math.ceil(props.data.length / colNum.value),
)
// 总高度
const totalHeight = computed(() => rowNum.value * props.itemHeight)
// 可视+缓冲行
const startIdx = computed(() =>
  Math.max(0, Math.floor(scrollTop.value / props.itemHeight - props.buffer) * colNum.value),
)
const endIdx = computed(() =>
  Math.min(props.data.length, Math.ceil((scrollTop.value + props.listHeight) / props.itemHeight + props.buffer) * colNum.value),
)

const offsetTop = computed(() =>
  Math.floor(startIdx.value / colNum.value) * props.itemHeight,
)
const visibleData = computed(() =>
  props.data.slice(startIdx.value, endIdx.value),
)

function onScroll(e: any) {
  scrollTop.value = e.detail.scrollTop || (e.detail?.scrollTop ?? 0)
}
</script>

<template>
  <scroll-view
    class="scroll"
    :scroll-y="true"
    :style="{ height: `${listHeight}px` }"
    :scroll-top="scrollTop"
    @scroll="onScroll"
  >
    <!-- 用撑高div营造虚拟滚动感 -->
    <view :style="{ height: `${totalHeight}px`, position: 'relative' }">
      <!-- 真正可见部分 -->
      <view
        class="grid-list"
        :style="{ position: 'absolute', left: 0, right: 0, top: `${offsetTop}px` }"
      >
        <view
          v-for="(item, idx) in visibleData"
          :key="itemKey ? item[itemKey] : idx"
          class="grid-item"
        >
          <slot :item="item" :index="startIdx + idx" />
        </view>
      </view>
    </view>
  </scroll-view>
</template>

<style scoped>
.grid-list {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  box-sizing: border-box;
}
.grid-item {
  min-width: 80px;
  box-sizing: border-box;
  flex: 1 0 0%;
  max-width: 25%;
  height: 100px;
  padding: 8px;
}
</style>
